using System;
using System.Collections.Generic;

namespace EternitySolver.Core
{
    public class Board
    {
        private readonly int _width;
        private readonly int _height;
        private readonly TileBag _tilebag;
        private readonly List<Tile> _boardplaces;

        public TileBag Tilebag { get { return _tilebag; } }
        public List<Tile> PlacedTiles { get { return _boardplaces; } }
        public int Width { get { return _width; } }
        public int Height { get { return _height; } }

        #region Factory Method to create a board.

        private static readonly Random Random = new Random();
        private static int GetRandomTileColor(int colors)
        {
            return Random.Next(1, colors);
        }
        public static Board Create(int width, int height, int colors)
        {
            var tileid = 1;
            var tilebag = new TileBag();
            var numberoftiles = (width * height) - 1;
            for (var position = 0; position <= numberoftiles; position++)
            {
                var top = Tile.EDGE_TILE;
                var right = Tile.EDGE_TILE;
                var bottom = Tile.EDGE_TILE;
                var left = Tile.EDGE_TILE;
                var tilePosition = ToPosition(position, width, height);

                switch (tilePosition)
                {
                    case BoardPosition.TopLeft:
                        if (width > 1) right = GetRandomTileColor(colors);
                        if (height > 1) bottom = GetRandomTileColor(colors);
                        break;
                    case BoardPosition.TopRight:
                        left = tilebag.Ordered[position - 1].Right;
                        if (height > 1) bottom = GetRandomTileColor(colors);
                        break;
                    case BoardPosition.BottomLeft:
                        if (height > 1) top = tilebag.Ordered[position - width].Bottom;
                        if (width > 1) right = GetRandomTileColor(colors);
                        break;
                    case BoardPosition.BottomRight:
                        if (width > 1) left = tilebag.Ordered[position - 1].Right;
                        if (height > 1) top = tilebag.Ordered[position - width].Bottom;
                        break;
                    case BoardPosition.TopEdge:
                        left = tilebag.Ordered[position - 1].Right;
                        right = GetRandomTileColor(colors);
                        bottom = GetRandomTileColor(colors);
                        break;
                    case BoardPosition.LeftEdge:
                        top = tilebag.Ordered[position - width].Bottom;
                        right = GetRandomTileColor(colors);
                        bottom = GetRandomTileColor(colors);
                        break;
                    case BoardPosition.RightEdge:
                        top = tilebag.Ordered[position - width].Bottom;
                        left = tilebag.Ordered[position - 1].Right;
                        bottom = GetRandomTileColor(colors);
                        break;
                    case BoardPosition.BottomEdge:
                        left = tilebag.Ordered[position - 1].Right;
                        top = tilebag.Ordered[position - width].Bottom;
                        right = GetRandomTileColor(colors);
                        break;
                    default:
                        top = tilebag.Ordered[position - width].Bottom;
                        left = tilebag.Ordered[position - 1].Right;
                        right = GetRandomTileColor(colors);
                        bottom = GetRandomTileColor(colors);
                        break;
                }
                tilebag.Add(new Tile(tileid, top, right, bottom, left));
                tileid++;
            }
            tilebag.Jumble();
            return new Board(width, height, tilebag);
        }

        #endregion

        private Board(int width, int height, TileBag tilebag)
        {
            _width = width;
            _height = height;
            _tilebag = tilebag;

            // create the boardplaces
            _boardplaces = new List<Tile>(tilebag.Ordered.Count);
            for (var i = 0; i <= tilebag.Ordered.Count - 1; i++) _boardplaces.Add(null);
        }

        private static BoardPosition ToPosition(int position, int width, int height)
        {
            var num = (width * height) - 1;
            if (position == 0) return BoardPosition.TopLeft;
            if (position == (width - 1)) return BoardPosition.TopRight;
            if (position == (num - width + 1)) return BoardPosition.BottomLeft;
            if (position == num) return BoardPosition.BottomRight;
            if (width >= 3 && position > 0 && position < (width - 1)) return BoardPosition.TopEdge;
            if (height >= 3 && position % width == 0) return BoardPosition.LeftEdge;
            if (height >= 3 && (position + 1) % width == 0) return BoardPosition.RightEdge;
            if (width >= 3 && position >= (num - width) && position < num) return BoardPosition.BottomEdge;
            return BoardPosition.NonEdge;
        }

        public TileSpecification SpecificationAt(int position)
        {
            var specification = new TileSpecification();
            var tilePosition = ToPosition(position, _width, _height);
            switch (tilePosition)
            {
                case BoardPosition.TopLeft:
                    specification.Left = Tile.EDGE_TILE;
                    specification.Top = Tile.EDGE_TILE;
                    if (_boardplaces[1] != null) specification.Right = _boardplaces[1].Left;
                    if (_boardplaces[_width] != null) specification.Bottom = _boardplaces[_width].Top;
                    break;
                case BoardPosition.TopRight:
                    specification.Top = Tile.EDGE_TILE;
                    specification.Right = Tile.EDGE_TILE;
                    if (_boardplaces[position - 1] != null) specification.Left = _boardplaces[position - 1].Right;
                    if (_boardplaces[position + _width] != null) specification.Bottom = _boardplaces[position + _width].Top;
                    break;
                case BoardPosition.BottomLeft:
                    specification.Left = Tile.EDGE_TILE;
                    specification.Bottom = Tile.EDGE_TILE;
                    if (_boardplaces[position - _width] != null) specification.Top = _boardplaces[position - _width].Bottom;
                    if (_boardplaces[position + 1] != null) specification.Right = _boardplaces[position + 1].Left;
                    break;
                case BoardPosition.BottomRight:
                    specification.Right = Tile.EDGE_TILE;
                    specification.Bottom = Tile.EDGE_TILE;
                    if (_boardplaces[position - 1] != null) specification.Left = _boardplaces[position - 1].Right;
                    if (_boardplaces[position - _width] != null) specification.Top = _boardplaces[position - _width].Bottom;
                    break;
                case BoardPosition.TopEdge:
                    specification.Top = Tile.EDGE_TILE;
                    if (_boardplaces[position - 1] != null) specification.Left = _boardplaces[position - 1].Right;
                    if (_boardplaces[position + 1] != null) specification.Right = _boardplaces[position + 1].Left;
                    if (_boardplaces[position + _width] != null) specification.Bottom = _boardplaces[position + _width].Top;
                    break;
                case BoardPosition.LeftEdge:
                    specification.Left = Tile.EDGE_TILE;
                    if (_boardplaces[position - _width] != null) specification.Top = _boardplaces[position - _width].Bottom;
                    if (_boardplaces[position + _width] != null) specification.Bottom = _boardplaces[position + _width].Top;
                    if (_boardplaces[position + 1] != null) specification.Right = _boardplaces[position + 1].Left;
                    break;
                case BoardPosition.RightEdge:
                    specification.Right = Tile.EDGE_TILE;
                    if (_boardplaces[position - 1] != null) specification.Left = _boardplaces[position - 1].Right;
                    if (_boardplaces[position - _width] != null) specification.Top = _boardplaces[position - _width].Bottom;
                    if (_boardplaces[position + _width] != null) specification.Bottom = _boardplaces[position + _width].Top;
                    break;
                case BoardPosition.BottomEdge:
                    specification.Bottom = Tile.EDGE_TILE;
                    if (_boardplaces[position - 1] != null) specification.Left = _boardplaces[position - 1].Right;
                    if (_boardplaces[position - _width] != null) specification.Top = _boardplaces[position - _width].Bottom;
                    if (_boardplaces[position + 1] != null) specification.Right = _boardplaces[position + 1].Left;
                    break;
                default:
                    if (_boardplaces[position - 1] != null) specification.Left = _boardplaces[position - 1].Right;
                    if (_boardplaces[position - _width] != null) specification.Top = _boardplaces[position - _width].Bottom;
                    if (_boardplaces[position + 1] != null) specification.Right = _boardplaces[position + 1].Left;
                    if (_boardplaces[position + _width] != null) specification.Bottom = _boardplaces[position + _width].Top;
                    break;
            }
            return specification;
        }
    }
}